﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Imgur.Net.Auth;
using Moq;
using NUnit.Framework;
using TestableHttpWebResponse;

namespace Imgur.Net.Test.Auth
{
    [TestFixture]
    public class RegisteredAuthenticationProviderTests
    {
        private const string clientId = "b56c4ebcd84a2c5";
        private const string clientSecret = "5b83ef6e0100a5a8544cc03f2e49c470d7f7cb2f";
        private const string tokenEndpoint = "test://api.imgur.com/oauth2/token";

        [TestFixtureSetUp]
        public void TestFixtureSetup()
        {
            WebRequest.RegisterPrefix("test", TestableWebRequestCreateFactory.GetFactory());
        }

        [Test]
        public void CreateInstanceTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction);

            Assert.IsNotNull(provider);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CreateInstanceNullClientIdTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new RegisteredAuthenticationProvider(null, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CreateInstanceNullClientSecretTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new RegisteredAuthenticationProvider(clientId, null,
                registrationTokenContextMock.Object, credentialsFunction);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CreateInstanceNullCurrentTokensTest()
        {
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                null, credentialsFunction);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CreateInstanceNullCredentialsFunctionTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            
            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CreateInstanceNullTokenEndpointTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction, null );
        }

        private class TestableProvider : RegisteredAuthenticationProvider
        {
            public TestableProvider(string clientId, string clientSecret, RegistrationTokenContext currentTokens, Func<Uri, RegistrationTokenContext> credentialsFunction) : base(clientId, clientSecret, currentTokens, credentialsFunction)
            {
            }

            public void DoFireRegTokensChanged()
            {
                FireRegistrationTokensChanged();
            }
        }

        [Test]
        public void FireRegistrationTokensChangedTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            var provider = new TestableProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction);

            Assert.IsNotNull(provider);

            ManualResetEvent mre = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mre.Set();
            };

            provider.DoFireRegTokensChanged();

            Assert.IsTrue(mre.WaitOne(1000*3), "Timeout waiting for event callback.");
        }

        [Test]
        public void GetAuthorizationContextExistingAuthTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            registrationTokenContextMock.Setup(x=>x.AccessToken).Returns("aaaaa").Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns("rrrrrrr").Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(3600)).Verifiable();

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction);

            Assert.IsNotNull(provider);

            ImgurAuthContext result = provider.GetAuthorizationContext();

            Assert.AreEqual(registrationTokenContextMock.Object.AccessToken, result.RegistrationContext.AccessToken);
            Assert.AreEqual(registrationTokenContextMock.Object.RefreshToken, result.RegistrationContext.RefreshToken);
            Assert.AreEqual(registrationTokenContextMock.Object.Expiration, result.RegistrationContext.Expiration);
        }

        [Test]
        public void GetAuthorizationContextRequiresLoginTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
            RegistrationTokenContext newRegTokenContext = new DefaultRegistrationTokenContext("a2", "r2", DateTime.Now.AddSeconds(500));

            ManualResetEvent mreCredentialsFunction = new ManualResetEvent(false);
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                Assert.IsNotNull(uri);
                mreCredentialsFunction.Set();
                return newRegTokenContext;
            };

            registrationTokenContextMock.Setup(x => x.AccessToken).Returns("aaaaa").Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns<string>(null).Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(3600)).Verifiable();

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction);

            Assert.IsNotNull(provider);

            ManualResetEvent mreTokensChangedEvent = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mreTokensChangedEvent.Set();
            };

            ImgurAuthContext result = provider.GetAuthorizationContext();

            Assert.IsTrue(mreCredentialsFunction.WaitOne(1000*3),
                "Timeout waiting for credentials function to be called.");
            Assert.IsTrue(mreTokensChangedEvent.WaitOne(1000*3),
                "Timeout waiting for reg tokens changed event to fire.");

            Assert.AreEqual(newRegTokenContext.AccessToken, result.RegistrationContext.AccessToken);
            Assert.AreEqual(newRegTokenContext.RefreshToken, result.RegistrationContext.RefreshToken);
            Assert.AreEqual(newRegTokenContext.Expiration, result.RegistrationContext.Expiration);
        }

        [Test]
        public void GetAuthorizationContextRequiresRefreshTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();
           
            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            registrationTokenContextMock.Setup(x => x.AccessToken).Returns<string>(null).Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns("rrrr").Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(3600)).Verifiable();

            var expectedRequest = new TestableWebRequest(new Uri(tokenEndpoint));
            expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", ResourceLoader.GetResourceText("Resources/Auth/refreshtoken.success.json"), false);
            TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction, tokenEndpoint);

            Assert.IsNotNull(provider);

            ManualResetEvent mreTokensChangedEvent = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mreTokensChangedEvent.Set();
            };

            ImgurAuthContext result = provider.GetAuthorizationContext();

            Assert.IsTrue(mreTokensChangedEvent.WaitOne(1000 * 3),
                "Timeout waiting for reg tokens changed event to fire.");

            Assert.AreEqual("r_aaaaa", result.RegistrationContext.AccessToken);
            Assert.AreEqual("r_rrrr", result.RegistrationContext.RefreshToken);
            Assert.LessOrEqual((result.RegistrationContext.Expiration - DateTime.Now.AddSeconds(3600)).Seconds, 2);
        }

        [Test]
        public void GetAuthorizationContextLoginExpiredTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();

            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            registrationTokenContextMock.Setup(x => x.AccessToken).Returns("aaaa").Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns("rrrr").Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(-600)).Verifiable();

            var expectedRequest = new TestableWebRequest(new Uri(tokenEndpoint));
            expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", ResourceLoader.GetResourceText("Resources/Auth/refreshtoken.success.json"), false);
            TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction, tokenEndpoint);

            Assert.IsNotNull(provider);

            ManualResetEvent mreTokensChangedEvent = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mreTokensChangedEvent.Set();
            };

            ImgurAuthContext result = provider.GetAuthorizationContext();

            Assert.IsTrue(mreTokensChangedEvent.WaitOne(1000 * 3),
                "Timeout waiting for reg tokens changed event to fire.");

            Assert.AreEqual("r_aaaaa", result.RegistrationContext.AccessToken);
            Assert.AreEqual("r_rrrr", result.RegistrationContext.RefreshToken);
            Assert.LessOrEqual((result.RegistrationContext.Expiration - DateTime.Now.AddSeconds(3600)).Seconds, 2);
        }

        [Test]
        [ExpectedException(typeof(RefreshTokenInvalidException))]
        public void RefreshReturnsHttpErrorTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();

            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            registrationTokenContextMock.Setup(x => x.AccessToken).Returns<string>(null).Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns("rrrr").Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(3600)).Verifiable();

            var expectedRequest = new TestableWebRequest(new Uri(tokenEndpoint));
            expectedRequest.EnqueueResponse(HttpStatusCode.BadRequest, "ERROR", ResourceLoader.GetResourceText("Resources/Auth/refreshtoken.success.json"), false);
            TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction, tokenEndpoint);

            Assert.IsNotNull(provider);

            ManualResetEvent mreTokensChangedEvent = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mreTokensChangedEvent.Set();
            };

            ImgurAuthContext result = provider.GetAuthorizationContext();

            /*
            Assert.IsTrue(mreTokensChangedEvent.WaitOne(1000 * 3),
                "Timeout waiting for reg tokens changed event to fire.");

            Assert.AreEqual("r_aaaaa", result.RegistrationContext.AccessToken);
            Assert.AreEqual("r_rrrr", result.RegistrationContext.RefreshToken);
            Assert.LessOrEqual((result.RegistrationContext.Expiration - DateTime.Now.AddSeconds(3600)).Seconds, 2);
             */
        }

        [Test]
        public void RefreshThrowsWebExceptionTest()
        {
            Mock<RegistrationTokenContext> registrationTokenContextMock = new Mock<RegistrationTokenContext>();

            Func<Uri, RegistrationTokenContext> credentialsFunction = (uri) =>
            {
                return null;
            };

            registrationTokenContextMock.Setup(x => x.AccessToken).Returns<string>(null).Verifiable();
            registrationTokenContextMock.Setup(x => x.RefreshToken).Returns("rrrr").Verifiable();
            registrationTokenContextMock.Setup(x => x.Expiration).Returns(DateTime.Now.AddSeconds(3600)).Verifiable();

            var expectedRequest = new TestableWebRequest(new Uri(tokenEndpoint));
            expectedRequest.EnqueueResponse(HttpStatusCode.OK, "Success", ResourceLoader.GetResourceText("Resources/Auth/refreshtoken.success.json"), true);
            TestableWebRequestCreateFactory.GetFactory().AddRequest(expectedRequest);

            var provider = new RegisteredAuthenticationProvider(clientId, clientSecret,
                registrationTokenContextMock.Object, credentialsFunction, tokenEndpoint);

            Assert.IsNotNull(provider);

            ManualResetEvent mreTokensChangedEvent = new ManualResetEvent(false);
            provider.RegistrationTokensChanged += (sender, e) =>
            {
                mreTokensChangedEvent.Set();
            };

            ImgurAuthContext result = provider.GetAuthorizationContext();

            Assert.IsTrue(mreTokensChangedEvent.WaitOne(1000 * 3),
                "Timeout waiting for reg tokens changed event to fire.");

            Assert.AreEqual("r_aaaaa", result.RegistrationContext.AccessToken);
            Assert.AreEqual("r_rrrr", result.RegistrationContext.RefreshToken);
            Assert.LessOrEqual((result.RegistrationContext.Expiration - DateTime.Now.AddSeconds(3600)).Seconds, 2);
        }
    }
}
